// Per-entity CRUD — URL `/admin/<entity>`. Everything here is DISCOVERED:
// useCapabilityManifest → deriveEntityAdmins gives this entity's list query +
// create/update/delete actions + the columns. We bind the shipped schema-driven
// components to those tags:
//   - <DataTable api="app" query={spec.list.tag}/>      — live, reactive rows
//   - <AutoForm  api="app" mutation={spec.create.tag}/> — fields from the input schema
//
// WRITES ARE GATED ON THE ACCESS THE API DECLARES, not on a scope string this
// page invented. `spec.create.guards` is the procedure's own `guards:` /
// `openAccess:`, straight off the capability map, so this gate and the server's
// check read the same data and cannot drift.
//
// The decision is THREE-valued, and `unknown` is SHOWN, not hidden: a guard with
// a `resource` extractor is answered per ROW by the server, and a browser
// holding only global scopes cannot pre-compute it. Hiding those would empty the
// admin for exactly the multi-tenant apps whose subjects carry no global scopes.
// The server stays the authorization boundary and answers a typed ScopeError.
//
// Each row has a provenance ("why is this here?") drawer (08) + delete; the
// layout's undo bar reverts the last change (00/09). Edit this file freely —
// it's your template code, not a sealed runtime feature.
import { useState, type ReactNode } from 'react'
import type { PageMeta } from '@voltro/web'
import { useParams, AutoForm, DataTable } from '@voltro/web'
import {
  useCapabilityManifest,
  deriveEntityAdmins,
  useAccessDecision,
  requiredScopes,
  openAccessReason,
  useMutation,
  useProvenance,
  type EntityAction,
  type EntityAdminSpec,
} from '@voltro/client'
import { T, useTFn } from '@voltro/i18n'
import { getCatalog } from '../../../locales/index'

export const renderMode = 'ssr' as const

export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
  title: getCatalog(locale)['meta.entity.title'],
})

type Row = Record<string, unknown>

// "Why is this value here?" — a one-shot provenance lookup (innovation/08).
// Its own component so useProvenance only runs while the drawer is open.
const ProvenanceDrawer = ({ table, id, onClose }: { readonly table: string; readonly id: string; readonly onClose: () => void }): ReactNode => {
  const { data, loading, error } = useProvenance('app', table, id)
  return (
    <div className="drawer">
      <h3>
        <T id="prov.title" values={{ table, id, code: (c: ReactNode) => <code>{c}</code> }} />
        {' '}
        <button type="button" className="btn ghost" style={{ float: 'right' }} onClick={onClose}><T id="prov.close" /></button>
      </h3>
      {loading ? <p className="muted"><T id="prov.loading" /></p> : null}
      {error ? <p className="muted"><T id="prov.none" /></p> : null}
      {data ? (
        <dl>
          <dt><T id="prov.lastWrite" /></dt><dd>{data.lastWrite?.at ?? '—'}</dd>
          <dt><T id="prov.byActor" /></dt><dd>{data.lastWrite?.actor?.id ?? '—'}{data.lastWrite?.actor?.via ? <T id="prov.via" values={{ via: data.lastWrite.actor.via }} /> : ''}</dd>
          <dt><T id="prov.attribution" /></dt><dd>{data.attribution}</dd>
          {data.derived ? <><dt><T id="prov.derived" /></dt><dd><T id="prov.yes" /></dd></> : null}
        </dl>
      ) : null}
    </div>
  )
}

// What the api requires for this action, in words — so an operator who hits a
// ScopeError can read the scope they are missing instead of guessing at it.
const AccessNote = ({ action }: { readonly action: EntityAction }): ReactNode => {
  const reason = openAccessReason(action.guards)
  const scopes = requiredScopes(action.guards)
  if (reason !== undefined) return <span className="meta"><T id="entity.openAccess" values={{ reason }} /></span>
  if (scopes.length > 0) return <span className="meta"><T id="entity.requires" values={{ scopes: scopes.join(', ') }} /></span>
  return <span className="meta"><T id="entity.accessUndeclared" /></span>
}

// The bound CRUD surface for one entity. A child component so its hooks
// (useAccessDecision/useMutation) get the real spec, never undefined.
const EntityAdmin = ({ spec }: { readonly spec: EntityAdminSpec }): ReactNode => {
  const [sel, setSel] = useState<string | null>(null)
  const t = useTFn()
  const create = useAccessDecision(spec.create.guards)
  const remove = useAccessDecision(spec.delete.guards)
  // Always-called; the tag is only used when spec.delete.tag exists (gated below).
  const del = useMutation<Record<string, string>>('app', spec.delete.tag ?? '__voltro.noop')
  // The column the api keys rows on — NOT a hard-coded `id`. A table keyed on
  // anything else would otherwise get an empty id sent on every delete.
  const pk = spec.pkColumn ?? 'id'

  const rowActions = (row: Row): ReactNode => {
    const id = String(row[pk] ?? '')
    return (
      <span className="row-actions">
        <button type="button" className="btn ghost" onClick={() => setSel(id)}><T id="entity.why" /></button>
        {spec.delete.tag && spec.editable && remove !== 'denied' && id ? (
          <button type="button" className="btn danger" onClick={() => void del.mutate({ [pk]: id })}><T id="entity.delete" /></button>
        ) : null}
      </span>
    )
  }

  return (
    <section>
      <h1>{spec.table}</h1>

      {/* `.serverOnly()` columns are absent from `spec.columns` by construction —
          they never cross the wire, and the runtime REFUSES a mutation input that
          sets one. Saying so beats a silently shorter column list. */}
      {spec.serverOnlyColumns.length > 0 ? (
        <p className="meta">
          <T id="entity.serverOnly" values={{ count: spec.serverOnlyColumns.length, names: spec.serverOnlyColumns.join(', ') }} />
        </p>
      ) : null}
      {spec.sensitiveColumns.length > 0 ? (
        <p className="meta">
          <T id="entity.sensitive" values={{ names: spec.sensitiveColumns.join(', ') }} />
        </p>
      ) : null}
      {!spec.editable ? <p className="meta"><T id="entity.notEditable" /></p> : null}

      {spec.create.tag && create !== 'denied' ? (
        <div className="section">
          <h2><T id="entity.create" /> <AccessNote action={spec.create} /></h2>
          {create === 'unknown' ? <p className="muted"><T id="entity.accessUnknown" /></p> : null}
          <AutoForm api="app" mutation={spec.create.tag} submitLabel={t('entity.addLabel', { table: spec.table })} />
        </div>
      ) : null}

      <div className="section">
        <h2><T id="entity.rows" /></h2>
        {spec.list.tag ? (
          <DataTable api="app" query={spec.list.tag} rowActions={rowActions} emptyText={t('entity.empty', { table: spec.table })} />
        ) : (
          <p className="muted"><T id="entity.noList" /></p>
        )}
        {sel ? <ProvenanceDrawer table={spec.table} id={sel} onClose={() => setSel(null)} /> : null}
      </div>
    </section>
  )
}

export default function EntityPage(): ReactNode {
  const { entity } = useParams<{ entity: string }>()
  const { manifest, loading } = useCapabilityManifest('app')
  const spec = manifest ? deriveEntityAdmins(manifest).find((e) => e.table === entity) : undefined

  if (loading && !manifest) {
    return <section><div className="skeleton" style={{ height: '8rem' }} /></section>
  }
  if (!spec) {
    return (
      <section>
        <h1>{entity}</h1>
        <p className="banner"><T id="entity.unknown" values={{ entity, code: (c: ReactNode) => <code>{c}</code> }} /></p>
      </section>
    )
  }
  return <EntityAdmin spec={spec} />
}
